package com.moleq.device;

import jp.co.casio.caios.framework.device.DeviceCommon;
import jp.co.casio.caios.framework.device.SerialCom;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;

public class BaseScanner implements IScanner {

	private int scan_format_status;
	private ScannerSetting mScannerSetting;
	private LocalDevice mDeviceState;
	private boolean mIsConnected;
	private boolean mScannerSwitch = false;
	private boolean mScannerPause = false;
	protected Thread mScannerThread = null;
	private IDeviceEvent mListener;
	private int mLimitLen = 15;
	private SerialCom mSerialCom;
	private int mPort;
	private int mBaudRate;
	private int mBitLen;
	private int mParityBit;
	private int mStopBit;
	private int mCutFromIdx;
	private int mCutLen;

	private String mDisableCmd;
	private String mEnableCmd;

	private final Handler mScannerHandler = new Handler() {

		@Override
		public void handleMessage(Message msg) {

			String code = msg.obj.toString().trim();
			
			String[] barcodes = code.split("<13>");
			
			for (int i = 0; i < barcodes.length; i++) {
				String result = "";
				int resultCodeLen = barcodes[i].length();
				int iLen = mCutFromIdx + mCutLen;

				if (resultCodeLen < mCutFromIdx + 1 || resultCodeLen > mLimitLen)
					return;

				if (resultCodeLen < iLen) {
					result =  barcodes[i];
				} else {
					result =  barcodes[i].substring(mCutFromIdx, iLen);
				}

				mListener.onScannerEvent(result);
			}
			
		}
	};
	

	private  String readScannerCOMport(){
		String str = null;
		byte[] data = new byte[70];
		StringBuilder sb = new StringBuilder();
		boolean cr = false;
		boolean lf = false;
		int retry = 5;	//It seems with a wait of 100, most of the time everything is read in one shot (but not always); 50 seems to never be 3 chunks
		int wait_between_reads = 50;
		boolean valid_fmt = true;	//Assume a good format and do checking for invalid

		//If there is no data waiting, don't bother
		if ((mSerialCom.getControl() & SerialCom.SERIAL_IO_CTS) == 0)
			return (str);
			
		mSerialCom.setControl(SerialCom.SERIAL_IO_DTR | SerialCom.SERIAL_IO_RTS);
		sleep(50);	//Give some time for the signals to settle

		int len = mSerialCom.readData(data);

		//If there was no data read, don't bother waiting for more
		if (len <= 0){
			mSerialCom.setControl(SerialCom.SERIAL_IO_DTR);
			return (str);
		}
		
		while (retry > 0){
			//If data was read...
			if (len > 0){
				
				//Go thru bytes read and convert to string
				for (int i = 0; i < len; i++) {
					byte b = data[i];

					//If special char 
					if ((0 <= b) && (b <= 31)){  
						sb.append(String.format("<%02d>", b));
						
						//If <CR> then set flag to get out
						if (b == 13)
							cr = true;
						//If <LF> then set flag
						else if (b == 10)
							lf = true;

						//If !<CR> && !<LF> then invalid char
						if ((b != 13) && (b != 10))
							valid_fmt = false;	//Invalid char for scanner data
					}
					//If "graphic" char 
					else if (b >= 127){
						sb.append(String.format("<%03d>", b));
						valid_fmt = false;	//Invalid char for scanner data
					}
					//Else normal char
					else
						sb.append(new String(new byte[] {b}));
				}
				
				str = sb.toString();
				
				//If <CR> || <LF> then get out
				if (cr || lf)
					break;
			}
			
			sleep(wait_between_reads);
			len = mSerialCom.readData(data);
			retry--;
		}
		
		//If there is already bad chars, don't bother checking any more
		if (valid_fmt){
			while (true){
				//If no <CR> then invalid format
				if (!cr){
					valid_fmt = false;
					break;
				}

				//If first char is numeric, invalid format
				if (Character.isDigit(str.charAt(0))){	
					valid_fmt = false;
					break;
				}
				
				//If len not enough, invalid format
				len = str.length();
				if ((!lf && (len < 5)) || (lf && (len < 9))){
					valid_fmt = false;
					break;
				}
				
				//not first char must be digit
				String sub = str.substring(len-4, len);
				
				//If the "last char" is not <CR> or <LF>, invalid format
				if (!sub.equals("<13>") && !sub.equals("<10>")){
					valid_fmt = false;
					break;
				}

				//If last char is <LF> then 2nd to last must be <CR>
				if (sub.equals("<10>")){
					if (!str.substring(len-8, len-4).equals("<13>")){
						valid_fmt = false;
						break;
					}
				}

				int test_len = (lf) ? (len-8) : (len-4);
				for (int i=1; (i<test_len) && valid_fmt; i++){
					if (!Character.isDigit(str.charAt(i))){
						valid_fmt = false;
						break;
					}
				}
				
				break;
			}
		}

		//If valid format, then set flag appropriately
		if (valid_fmt)
			scan_format_status  = 1;
		else
			scan_format_status = 2;
		
		mSerialCom.setControl(SerialCom.SERIAL_IO_DTR);

		return(str);
	}

	private void sleep(int time) {
		try {
			Thread.sleep(time);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	// read scanner buffer
	protected void readScannerBuffer() {
		readScannerCOMport();
		while (mScannerSwitch) {

			try {

				String scanner_text = readScannerCOMport();
				
				if (!mScannerPause) {
					if ((scanner_text != null) && scanner_text.length() > 0) {
						Message message = mScannerHandler.obtainMessage(0, scanner_text);
						mScannerHandler.sendMessage(message);
					}

				}

			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}
	}

	@Override
	public void onDisableScanner() {
		// TODO Auto-generated method stub
		if (!mDisableCmd.equals(""))
			sendCmd(mDisableCmd);

		mScannerPause = true;

	}

	@Override
	public void onEnableScanner() {
		
		if (!mEnableCmd.equals(""))
			sendCmd(mEnableCmd);

		mScannerSwitch = true;
		mScannerPause = false;
		if (mScannerThread == null) {
			mScannerThread = new Thread(new Runnable() {
				@Override
				public void run() {

					readScannerBuffer();

				}
			});

			mScannerThread.setDaemon(true);
			mScannerThread.start();
		}

		if (mScannerThread.getState() != Thread.State.TERMINATED) {

		} else {

			mScannerThread = new Thread(new Runnable() {
				@Override
				public void run() {
					
					readScannerBuffer();

				}
			});
			mScannerThread.setDaemon(true);
			mScannerThread.start();
		}

	}

	@Override
	public LocalDevice getState() {
		return this.mDeviceState;
	}

	@Override
	public void onDisconnect() {
		if (this.mDeviceState == LocalDevice.OPEN_STATE) {
			if (mScannerSwitch) {
				mScannerSwitch = false;
				sleep(500);
			}

			if (mSerialCom != null) {
				mSerialCom.disconnectCom();
				mSerialCom.close();
				mSerialCom = null;
				mIsConnected = false;
				mDeviceState = LocalDevice.CLOSE_STATE;
			}
		}
	}

	@Override
	public void onConnect() {
		if (mSerialCom == null) {
			mSerialCom = new SerialCom();
		}

		mIsConnected = false;

		boolean result = false;
		int response = 0;

		response = mSerialCom.open(mPort, DeviceCommon.DEVICE_MODE_COMMON, DeviceCommon.DEVICE_HOST_LOCALHOST);

		if (response != 0) {
			mDeviceState = LocalDevice.CLOSE_STATE;

			mListener.onErrorEvent("Faild to open Serial port.");
			return;
		}

		result = mSerialCom.connectCom(mBaudRate, mBitLen, mParityBit, mStopBit, SerialCom.SERIAL_FLOW_NON);

		if (result != true) {
			mDeviceState = LocalDevice.CLOSE_STATE;

			mListener.onErrorEvent("Faild to connect serial port.");
			return;
		}

		mSerialCom.setControl(SerialCom.SERIAL_IO_CTS);
		mSerialCom.setControl(SerialCom.SERIAL_IO_RTS);

		mIsConnected = true;
		mDeviceState = LocalDevice.OPEN_STATE;

	}

	@Override
	public void sendCmd(String strCommand) {
		int response = 0;
		byte[] bCmd = strCommand.getBytes();

		if (mSerialCom != null) {
			response = mSerialCom.writeData(bCmd, bCmd.length);
		}
		if (response != 0) {
			mListener.onErrorEvent("Serial write data error.");
		}
	}

	@Override
	public void setCallbackEvent(IDeviceEvent impl) {
		mListener = impl;

	}

	@Override
	public void setComPort(int comPort) {
		// TODO Auto-generated method stub

	}

	@Override
	public void setScannerSetting(ScannerSetting setting) {
		mScannerSetting = setting;

		// set port
		switch (setting.getPort()) {
		case 1:
			mPort = SerialCom.SERIAL_TYPE_COM1;
			break;
		case 2:
			mPort = SerialCom.SERIAL_TYPE_COM2;
			break;
		case 3:
			mPort = SerialCom.SERIAL_TYPE_COM3;
			break;
		case 4:
			mPort = SerialCom.SERIAL_TYPE_COM4;
			break;
		case 5:
			mPort = SerialCom.SERIAL_TYPE_COM5;
			break;

		}

		// set baud rate
		String baudrate = setting.getBaud();
		if (baudrate.equals("38400")) {
			mBaudRate = SerialCom.SERIAL_BOUDRATE_38400;
		} else if (baudrate.equals("19200")) {
			mBaudRate = SerialCom.SERIAL_BOUDRATE_19200;
		} else if (baudrate.equals("9600")) {
			mBaudRate = SerialCom.SERIAL_BOUDRATE_9600;
		} else if (baudrate.equals("4800")) {
			mBaudRate = SerialCom.SERIAL_BOUDRATE_4800;
		} else if (baudrate.equals("2400")) {
			mBaudRate = SerialCom.SERIAL_BOUDRATE_2400;
		} else if (baudrate.equals("1200")) {
			mBaudRate = SerialCom.SERIAL_BOUDRATE_1200;
		}

		// set bit len
		switch (setting.getDataBits()) {
		case 7:
			mBitLen = SerialCom.SERIAL_BITLEN_7;
			break;
		case 8:
			mBitLen = SerialCom.SERIAL_BITLEN_8;
			break;
		}

		// set parity
		String parity = setting.getParity();
		if (parity.equals("E")) {
			mParityBit = SerialCom.SERIAL_PARITY_EVEN;
		} else if (parity.equals("N")) {
			mParityBit = SerialCom.SERIAL_PARITY_NON;
		} else if (parity.equals("O")) {
			mParityBit = SerialCom.SERIAL_PARITY_ODD;
		}

		// set Stop Bit
		switch (setting.getStopBits()) {
		case 1:
			mStopBit = SerialCom.SERIAL_STOP_1;
			break;
		case 2:
			mStopBit = SerialCom.SERIAL_STOP_2;
			break;

		}

		// set enable command
		mEnableCmd = DeviceUtil.getStringCommand(setting.getEnableCmd());

		// set disable command
		mDisableCmd = DeviceUtil.getStringCommand(setting.getEnableCmd());

		// set cut buffer
		mCutLen = 0;
		mCutFromIdx = 0;
		String[] sdata = setting.getDataFormat().split(",");
		if (sdata.length > 0) {
			mCutFromIdx = Integer.valueOf(sdata[0]) - 1;
			if (TextUtils.isDigitsOnly(sdata[1])) {
				mCutLen = Integer.valueOf(sdata[1]);
			}
		}
	}

	@Override
	public void onClearBuffer() {

		sleep(500);
		mSerialCom.readData(new byte[1024]);

	}

	@Override
	public boolean isConnected() {
		// TODO Auto-generated method stub
		return mIsConnected;
	}

}
